#pragma once

#include "HidDevice.h"
#include "Win32Utils.h"

#include <Windows.h>
#include <hidsdi.h>
// wil should be included after system API headers
#include "wil/resource.h"

#include <algorithm>
#include <array>
#include <map>
#include <unordered_map>
#include <utility>
#include <vector>

#include <cstddef>


/* Re: multiple controllers
* It's always safe to connect multiple HID controllers that do not have overlapping
* controls. Multiple controllers with overlapping buttons is fine. Multiple controllers
* with overlapping slider segments will not work well.
* 
* TLAC feeds button input to the game in a "processed" state, with separate values for
* level and edge states. For HID controllers, each Top Level Collection maintains a
* separate set of button states, which are then unified and presented to TLAC each tick.
* 
* On the other hand, TLAC feeds slider values in the "raw" state, so there is only
* one slider state shared with all HID controllers.
*/


namespace TLAC::Input::RawInputCustomHidController
{
	// Top Level Collection Usage values
	static const USAGE TLC_USAGE_PAGE = 0xFF39;
	static const USAGE TLC_USAGE = 0x1;

	// Input buttons
	static constexpr USAGE BUTTON_USAGE_PAGE = 0x09;
	enum class ButtonIndex {
		/* The actual usage values for each button is BUTTON_USAGE_MIN + ButtonIndex.
		* There's no code that actually needs to know the usage value for each button.
		*/
		Triangle = 0,
		Square,
		Cross,
		Circle,
		Start,
		Test,
		Service
	};
	static constexpr USAGE BUTTON_USAGE_MIN = 0x01;
	static constexpr USAGE BUTTON_USAGE_MAX = 0x07;
	static constexpr std::size_t num_input_buttons = BUTTON_USAGE_MAX - BUTTON_USAGE_MIN + 1;
	// Input values
	static constexpr USAGE DIVA_USAGE_PAGE = 0xFF39;
	static constexpr USAGE TOUCH_SLIDER_USAGE_MIN = 0x20;
	static constexpr USAGE TOUCH_SLIDER_USAGE_MAX = 0x3F;
	static constexpr std::size_t num_input_slider_values = TOUCH_SLIDER_USAGE_MAX - TOUCH_SLIDER_USAGE_MIN + 1;
	static_assert(num_input_slider_values == 32);
	static constexpr ULONG TOUCH_SLIDER_DEFAULT_MAX = 255;
	// Output values
	static constexpr USAGE LED_GLOBAL_BRIGHTNESS_USAGE = 0x40;
	static constexpr ULONG LED_GLOBAL_BRIGHTNESS_DEFAULT_MAX = 63;
	static constexpr USAGE LED_BRG_USAGE_MIN = 0x41;
	static constexpr USAGE LED_BRG_USAGE_MAX = 0xA0;
	static constexpr std::size_t num_output_led_brg_values = LED_BRG_USAGE_MAX - LED_BRG_USAGE_MIN + 1;
	static_assert(num_output_led_brg_values == 96);
	static constexpr ULONG LED_BRG_DEFAULT_MAX = 255;
	// Output "buttons"
	static constexpr USAGE BUTTON_LIGHTS_USAGE_MIN = 0xA1;
	static constexpr USAGE BUTTON_LIGHTS_USAGE_MAX = 0xA4;
	static constexpr std::size_t num_button_lights = BUTTON_LIGHTS_USAGE_MAX - BUTTON_LIGHTS_USAGE_MIN + 1;
	static_assert(num_button_lights == 4);
	static constexpr USAGE PARTITION_LIGHTS_USAGE_MIN = 0xA5;
	static constexpr USAGE PARTITION_LIGHTS_USAGE_MAX = 0xAA;
	static constexpr std::size_t num_partition_lights = PARTITION_LIGHTS_USAGE_MAX - PARTITION_LIGHTS_USAGE_MIN + 1;
	static_assert(num_partition_lights == 6);

	struct InputsAvailable {
		std::array<bool, num_input_buttons> buttons{};
		std::array<bool, num_input_slider_values> values{};
		bool has_any_button{};
		bool has_any_value{};
	};

	struct UsageToDataIndex {
		LONG logical_max{};
		// logical minimum is assumed to be 0 always
		USHORT data_index{};
		bool is_present{};
	};

	struct InputsInReport {
		std::array<UsageToDataIndex, num_input_buttons> buttons{};
		std::array<UsageToDataIndex, num_input_slider_values> slider_values{};
		bool buttons_present{};
		bool slider_present{};

		void update_presents() {
			buttons_present = std::any_of(buttons.begin(), buttons.end(), [](UsageToDataIndex entry) {return entry.is_present; });
			slider_present = std::any_of(slider_values.begin(), slider_values.end(), [](UsageToDataIndex entry) {return entry.is_present; });
		}
	};

	struct OutputsInReport {
		std::array<UsageToDataIndex, num_output_led_brg_values> slider_leds{};
		std::array<UsageToDataIndex, num_button_lights> button_lights{};
		std::array<UsageToDataIndex, num_partition_lights> partition_lights{};
		UsageToDataIndex slider_global_brightness{};
	};

	// The game stores slider values in uint32_t even though the maximum is 255. Don't ask me why.
	using slider_value_t = std::uint32_t;

	struct SliderValuesReport {
		std::array<bool, num_input_slider_values> has_values{};
		std::array<slider_value_t, num_input_slider_values> values{};
	};


	/* Handler for each HID Diva controller device.
	* Each instance of this class is responsible for one device handle, which
	* corresponds to one Top Level Collection, and may have several input reports
	* and output reports.
	*/
	class CustomHidControllerDevice
	{
		friend class CustomHidControllerHandler;
	public:
		/* Within each tick, methods are called in this order:
		* BeginTickMessages, [OpenDevice,] [HandleInput,] EndTickMessages
		* Class instances are constructed just before call to OpenDevice, so for the first tick 
		* BeginTickMessages will not be called.
		*/

		void BeginTickMessages();
		bool OpenDevice(HANDLE handle);

		// Update button states internally, but put slider updates into slider_reports
		void HandleInput(RAWINPUT& input, std::vector<SliderValuesReport>& slider_reports);
		void EndTickMessages();

	private:
		std::unordered_map<UCHAR, InputsInReport> inputs_by_report_id_{};
		std::unordered_map<UCHAR, OutputsInReport> outputs_by_report_id_{};

		std::vector<HIDP_DATA> output_data_buffer_{};
		std::unordered_map<UCHAR, RawInputDetails::AsyncWrite> pending_output_writes_{};

		/* Since order of member destruction is reverse of declaration order, 
		put device_ below pending_output_writes_ so the device file handle is closed first */
		RawInputDetails::HidDevice device_;

		InputsAvailable inputs_available_{};

		std::array<bool, num_input_buttons>
			input_buttons_state_{},
			input_buttons_tapped_{},
			input_buttons_released_{};
	};


	/* Handler for custom-made HID Diva controllers.
	* This class is responsible for handling device arrival and removal, and delegating
	* messages for each device to its own CustomHidControllerDevice instance.
	*/
	class CustomHidControllerHandler
	{
	public:
		/* Add any devices that should be RegisterRawInputDevices()'ed to the provided vector.
		* Each Handler class should add devices that it can handle.
		* The RIDEV_INPUTSINK flag and the correct hWnd will be added by the caller.
		*/
		void AddDevicesToRegister(std::vector<RAWINPUTDEVICE>& devices) const;

		void BeginTickMessages();
		void HandleDeviceArrival(HANDLE handle, const RID_DEVICE_INFO& device_info);
		void HandleDeviceRemoval(HANDLE handle);
		void HandleInput(RAWINPUT& input);
		void EndTickMessages();

		const InputsAvailable& GetInputsAvailable() const {
			return inputs_available_;
		}

		const std::array<bool, num_input_buttons>& GetInputButtonsState() const {
			return input_buttons_state_;
		}

		const std::array<bool, num_input_buttons>& GetInputButtonsTapped() const {
			return input_buttons_tapped_;
		}

		const std::array<bool, num_input_buttons>& GetInputButtonsReleased() const {
			return input_buttons_released_;
		}

		const std::vector<std::array<slider_value_t, num_input_slider_values>>& GetSliderUpdates() const {
			return input_slider_updates_;
		}
	private:
		void UpdateInputsAvailable();
		void CollectInputStates();

		std::unordered_map<HANDLE, CustomHidControllerDevice> devices_;

		InputsAvailable inputs_available_{};
		std::array<bool, num_input_buttons>
			input_buttons_state_{},
			input_buttons_tapped_{},
			input_buttons_released_{};
		std::vector<SliderValuesReport> input_slider_reports_{};  // raw slider updates
		std::array<slider_value_t, num_input_slider_values> input_slider_state_{};  // current slider values
		std::vector<std::array<slider_value_t, num_input_slider_values>> input_slider_updates_{};  // processed list of full-slider updates
	};
}